﻿package  
{
	import com.adamatomic.flixel.*;

	public class GameState extends FlxState
	{
		// Embedded data
		//
		[Embed(source = "../data/cursor.png")] private var mImgCursor:Class;
		[Embed(source = "../data/bg.png")] private var mImgBG:Class;
		[Embed(source = "../data/titletext.png")] private var mImgTitleText:Class;
		[Embed(source = "../data/starttexton.png")] private var mImgStartTextOn:Class;
		[Embed(source = "../data/starttextoff.png")] private var mImgStartTextOff:Class;
		[Embed(source = "../data/refresh.png")] private var mImgRefreshOff:Class;
		[Embed(source = "../data/refresh_selected.png")] private var mImgRefreshOn:Class;		
        
		// Stores the current state of the board as we know it.
		//
		private var mBoardState:Array = new Array( );
		
		// The width and height of the board. It is always square, and the
		// standard is 9x9.
		//
		private var mBoardSize:Number;
		
		// Solver used to actually do the work of solving this puzzle
		//
		private var mSolver:Solver;
		
		// Handles the controlling of the solver playback LOD; how much we try to
		// solve with each pass.
		//
		private var mPlaybackLOD:PlaybackLOD;
        
        // Used to show the results of the solver.
        //
        private var mResultsPopup:Popup;
		
		// For starting and stoping the solver
		//
		private var mRunSolver:Boolean;
		
		// We don't want to highlight anything until the solver has been started
		// at least once.
		//
		private var mSolverBeenRun:Boolean;
        
        // Since playback can be broken up between update passes, we need to store
        // the loop data in member variables so we can continue where we left off
        // in the next update.
        //
        private var mRowUpdate:Number;
        private var mCellUpdate:Number;
        
        // Stores that previously selected tile, so that it can be unselected when
        // the next tile is selected.
        //
        private var mLastSelected:Tile;
        
        // The button used to start the solver.
        //
        private var mStartButton:FlxButton;
        
        // The button to restart the state of the game.
        //
        private var mRefreshButton:FlxButton;
		
		// A basic sudoku set up.  -1 represents unsolved squares.  Ever other number is 
		// considered a confirmed number.
		//
        
		private const mTestBoard:Array = new Array( -1, -1, -1,  -1, -1, -1,  -1,  8, -1,
												    -1,  7, -1,   8,  1, -1,  -1,  5, -1, 
													-1, -1,  2,   5, -1,  4,   9,  6, -1,
													                                  
													 2,  1, -1,   6, -1, -1,   3, -1,  8, 
													-1, -1, -1,   3, -1,  2,  -1, -1, -1, 
													 3, -1,  9,  -1, -1,  7,  -1,  2,  4, 
													                                  
													-1,  2,  1,   9, -1,  8,   6, -1, -1, 
													-1,  5, -1,  -1,  2,  1,  -1,  3, -1, 
													 8,  3, -1,  -1, -1, -1,  -1, -1, -1 );
		/*
		private const mTestBoard:Array = new Array( -1, -1,  9,   7,  4,  8,  -1, -1, -1,
												     7, -1, -1,  -1, -1, -1,  -1, -1, -1, 
													-1,  2, -1,   1, -1,  9,  -1, -1, -1,
													                                  
													-1, -1,  7,  -1, -1, -1,   2,  4, -1, 
													-1,  6,  4,  -1,  1, -1,   5,  9, -1, 
													-1,  9,  8,  -1, -1, -1,   3, -1, -1, 
													                                  
													-1, -1, -1,   8, -1,  3,  -1,  2, -1, 
													-1, -1, -1,  -1, -1,  1,  -1, -1,  6, 
													-1, -1, -1,   2,  7,  5,   9, -1, -1 ); 
        */
                                                    
		// Constructor
		//
		public function GameState( ) 
		{
			// Get the background images rendering at the back.
			this.add( new FlxSprite( mImgBG ) );
			this.add( new FlxSprite( mImgTitleText ) );
			
			// Eventually this should be customizable
			mBoardSize = 9;
			
			// A spacer to make the groups of 9 more obvious.
			var spacerX:Number = 0;
			var spacerY:Number = 0;
			
			// The amount to space them by.
			var spacerSize:Number = 1;
			
			var testCount:Number = 0;
			
			// Init the game state
			for ( var row:Number = 0; row < mBoardSize; row++ )
			{
				// Our board state is going to be a 2D array
				mBoardState[row] = new Array( );
				
				// Modifier to add space between groups
				if ( row % 3 == 0 && row != 0 )
				{
					spacerY += spacerSize;
				}
				
				// Now set each element in the array
				for ( var cell:Number = 0; cell < mBoardSize; cell++ )
				{
					// Create a tile for each cell in the board.
					mBoardState[row][cell] = new Tile( );
					
					// Limit the number of times we have to dereference into the
					// arrays.
					var tile:Tile = mBoardState[row][cell];
					
					// For testing...
					if ( mTestBoard[ testCount ] != -1 )
					{
						tile.SetValueState( mTestBoard[ testCount ] - 1, Tile.VALUE_STATE_CONFIRMED );
                        tile.SetIsLocked( true );
					}
					
					// Modifier to add space between groups
					if ( cell % 3 == 0 && cell != 0 )
					{
						spacerX += spacerSize;
					}
					
					// The position of the tile (top left)
					var xPos:Number = cell * tile.mBGWidth + spacerX + 60;
					var yPos:Number = row * tile.mBGWidth + spacerY + 20;
					
					// Add the text for that
					tile.mBG = this.add( new FlxSprite( tile.mImgBG, xPos, yPos, true, false, tile.mBGWidth ) ) as FlxSprite;
					tile.mText = this.add( new FlxText( xPos, yPos, tile.mBGWidth + 2, tile.mBGWidth + 2, tile.GetDisplayString( ), 0xff333333, null, 16, "center" ) ) as FlxText;
                    tile.mPossibleText = this.add( new FlxText( xPos, yPos, tile.mBGWidth + 2, tile.mBGWidth + 2, "", 0xff333333, null, 5, "left" ) ) as FlxText;
					
					testCount++;
				}
				
				// Reset the X spacer for the next row
				spacerX = 0;
			}
			
			// Our instance of the class which does all the solving.
			mSolver = new Solver( );
			
			// This object controls the playback of the solver; how much is
			// solved each pass.
			mPlaybackLOD = new PlaybackLOD( this );
            
			// Add a button for sttarting the solver
			var buttonWidth:Number = 80;
			var buttonHeight:Number = 15;

			mStartButton = this.add( new FlxButton( 293, 
								     73,
								     new FlxSprite( mImgStartTextOff ),
								     OnStartSolver,
								     new FlxSprite( mImgStartTextOn )
								     ) ) as FlxButton;
            
            // The button used to refresh the screen.
            //
            mRefreshButton = this.add( new FlxButton( 292, 
                                       4,
                                       new FlxSprite( mImgRefreshOff ),
                                       Reset,
                                       new FlxSprite( mImgRefreshOn )
                                       ) ) as FlxButton;
                         
            // This will eventually show the results.
            mResultsPopup = new Popup( this );
            
			// Start the solver at 0,0.
            mRowUpdate = 0;
            mCellUpdate = 0;
			
			// Turn the solver off by default.
			mRunSolver = false;
			mSolverBeenRun = false;
			
			FlxG.setCursor( mImgCursor );
		}
		
		// Override the update function defined by the FlxState framework.
		//
		override public function update() : void
		{
			super.update( );
			
			if ( mRunSolver )
			{
                switch( mPlaybackLOD.GetCurrentLOD( ) )
                {
                    case PlaybackLOD.PLAYBACK_LOD_ONE_CELL:
                    {
                        UpdateOneCell( );
                        break;
                    }
                    case PlaybackLOD.PLAYBACK_LOD_ONE_PASS:
                    {
                        UpdateOnePass( );
                        break;
                    }
                    case PlaybackLOD.PLAYBACK_LOD_FULL_PASS:
                    {
                        UpdateFullPass( );
                        break;
                    }
                }
                
            }
		}
        
		// Attempts to solve one cell on the board, and then pauses playback.
		//
        private function UpdateOneCell( ) : void
        {
            // We need to make sure we don't get stuck in an infinite loop when the 
            // puzzle has already been solved.  To avoid this we will keep track of
            // the number of solved tiles.  If it turns out to be every tile is solved
            // than we will exit.
            var numSolved:Number = 0;
            
            // It's not enough to just attempt to solve the next tile, and stop.  If that
            // tile is already solved than the result will be confusing to the user; 
            // appearing like a glitch in the solve button.
            var unsolvedFound:Boolean = false;
            
            // Warning: Infinite loop possible for impossible puzzles.
            //
            while ( !unsolvedFound && ( numSolved < mBoardSize * mBoardSize ) )
            {
                // Don't try to solve a cell that has already been solved
                if ( !mBoardState[mRowUpdate][mCellUpdate].HasBeenSolved( ) )
                {
                    SetSelectedTile( mRowUpdate, mCellUpdate );
                    
                    mSolver.SolveViaRow( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                    mSolver.SolveViaCol( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                    mSolver.SolveViaBlock( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                    mSolver.SolveViaSelf( mRowUpdate, mCellUpdate, mBoardState );
                    
                    // We have found an unsolved tile.  We may or may not have solved it here, but
                    // we tried, and that's all it takes to consider our job done for this pass.
                    unsolvedFound = true;
                }
                else
                {
                    numSolved++;
                }
                
                // We have look at another cell.
                mCellUpdate++;
                
                // Make sure we loop when we hit the end of the row
                if ( mCellUpdate >= mBoardSize )
                {
                    // Cell is back to zero...
                    mCellUpdate = 0;
                    
                    // .. and we continue to the next row.
                    mRowUpdate++;
                    
                    // The we also need to handle the case where we hit the end of the 
                    // board completely.
                    if ( mRowUpdate >= mBoardSize )
                    {
                        // In which case row also gets set back to zero and we start again.
                        mRowUpdate = 0;
                    }
                }
            }
            
            PauseSolver( );
        }
        
		// Updates every cell on the board once, and then pauses playback.
		//
        private function UpdateOnePass( ) : void
        {
            // Init the game state
            for ( ; mRowUpdate < mBoardSize; mRowUpdate++ )
            {
                // Now set each element in the array
                for ( ; mCellUpdate < mBoardSize; mCellUpdate++ )
                {
                    if ( !mBoardState[mRowUpdate][mCellUpdate].HasBeenSolved( ) )
                    {
                        SetSelectedTile( mRowUpdate, mCellUpdate );
                        
                        mSolver.SolveViaRow( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                        mSolver.SolveViaCol( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                        mSolver.SolveViaBlock( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                        mSolver.SolveViaSelf( mRowUpdate, mCellUpdate, mBoardState );
                    }
                }
                
                // We can't update this in the for loop because we want it to start where
                // we ended the last update (in the case where it was stopped mid update).
                mCellUpdate = 0;
            }
            
            // We can't update this in the for loop because we want it to start where
            // we ended the last update (in the case where it was stopped mid update).
            mRowUpdate = 0;
            
            PauseSolver( )
        }
        
		// Attempts to solve the entire board until either success or stalemate.
		//
        private function UpdateFullPass( ) : void
        {
            // We are going to determine the board is solved when every piece we
            // pass is marked as solved.  We need to keep track of that number as
            // we go.
            var numSolved:Number = 0;
            
            // Have we solved every piece?
            // Warning: Infinite loop on unsolvable boards!
            while ( numSolved < mBoardSize * mBoardSize )
            {
                // We are starting the loop over again, so we need to reset
                // how many have been solved.
                numSolved = 0;
                
                // Init the game state
                for ( ; mRowUpdate < mBoardSize; mRowUpdate++ )
                {
                    // Now set each element in the array
                    for ( ; mCellUpdate < mBoardSize; mCellUpdate++ )
                    {
                        if ( !mBoardState[mRowUpdate][mCellUpdate].HasBeenSolved( ) )
                        {
                            SetSelectedTile( mRowUpdate, mCellUpdate );
                            
                            mSolver.SolveViaRow( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                            mSolver.SolveViaCol( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                            mSolver.SolveViaBlock( mRowUpdate, mCellUpdate, mBoardState, mBoardSize );
                            mSolver.SolveViaSelf( mRowUpdate, mCellUpdate, mBoardState );
                        }
                        else
                        {
                            // The piece has already been solved, so add it to the list.
                            numSolved++;
                        }
                    }
                    
                    // We can't update this in the for loop because we want it to start where
                    // we ended the last update (in the case where it was stopped mid update).
                    mCellUpdate = 0;
                }
                
                // We can't update this in the for loop because we want it to start where
                // we ended the last update (in the case where it was stopped mid update).
                mRowUpdate = 0;
            }
            
            PauseSolver( );          
        }
		
		// Override of the Flixel render pass.
		//
		override public function render( ) : void
		{
			// Loop through each row
			for ( var row:Number = 0; row < mBoardSize; row++ )
			{
				// Now each tile within that row.
				for ( var cell:Number = 0; cell < mBoardSize; cell++ )
				{
					mBoardState[row][cell].UpdateDisplay( );
				}
			}
			
			super.render( );
		}
        
        // Called back from the popup to let us know that it has been closed.
        //
        public function OnResultPopupClose( ) : void
        {
            // The buttons would have been disabled when the popup was shown.
            mPlaybackLOD.EnableButtons( );
            mRefreshButton.active = true;
        }
		
		// Initiates the solver.
		//
		private function OnStartSolver( ) : void
		{
			mRunSolver = true;
			mSolverBeenRun = true;
		}
        
        // Pauses the solver and checks for a finished condition.
        //
        private function PauseSolver( ) : void
        {
            // Disable the solver
            mRunSolver = false;
            
            // Loop through all the tiles and check if they have all been solved.
            // This could be cleaned up to only check for a single unsolved block.
            //
            var numSolved:Number = 0;
			for ( var row:Number = 0; row < mBoardSize; row++ )
			{
				for ( var cell:Number = 0; cell < mBoardSize; cell++ )
				{
                    if ( mBoardState[row][cell].HasBeenSolved( ) )
                    {
                        numSolved++;
					}
				}
			}
            
            if ( numSolved >= mBoardSize * mBoardSize )
            {
                // Flash the screen for a little excitement...
                FlxG.flash(0xffffffff, 0.5);
                
                mPlaybackLOD.DisableButtons( );
                mStartButton.active = false;
                mRefreshButton.active = false;
                
                mResultsPopup.Show( "COMPLETE", OnResultPopupClose );
            }
        }
        
        // Helper function for changing from one selected tile to another.
        //
        private function SetSelectedTile( row:Number, cell:Number ) : void
        {
            // If we have a previously selected tile, set it back to unselected
            if ( mLastSelected) 
            {
                mLastSelected.SetIsSelected( false );
            }
            
            // Select the new tile
            mBoardState[row][cell].SetIsSelected( true );
            
            // Store the tile so that we can unselect it later.
            mLastSelected = mBoardState[row][cell];
        }
        
        // Resets the board back to it's original state.
        //
        private function Reset( ) : void
        {
			// Start the solver at 0,0.
            mRowUpdate = 0;
            mCellUpdate = 0;
			
			// Turn the solver off by default.
			mRunSolver = false;
			mSolverBeenRun = false;
            
            var testCount:Number = 0;
            
            // Init the game state
			for ( var row:Number = 0; row < mBoardSize; row++ )
			{
				// Now set each element in the array
				for ( var cell:Number = 0; cell < mBoardSize; cell++ )
				{
					// Limit the number of times we have to dereference into the
					// arrays.
					var tile:Tile = mBoardState[row][cell];
                    
                    // To start put the tile back to a default state.
                    tile.Reset( );
					
					// For testing...
					if ( mTestBoard[ testCount ] != -1 )
					{
						tile.SetValueState( mTestBoard[ testCount ] - 1, Tile.VALUE_STATE_CONFIRMED );
                        tile.SetIsLocked( true );
					}

					testCount++;
				}
			}  
            
            // This was disabled when the complete popup came up, but we don't restore it
            // until the board is reset since the puzzle has already been solved.
            mStartButton.active = true;
        }
	}	
}